import json
import logging

import markdown
import redis
from django.conf import settings
from django.core.cache import caches
from django.db import connection
from django.db.models import Q
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from rest_framework import viewsets, exceptions
from rest_framework.generics import ListAPIView
from rest_framework.response import Response

from .models import Post, Category, Link
from .pagination import HomePagePagination, PostsPagination
from .serializers import (
    PostSerializer, CategorySerializer,
    PostsSerializer, PopularPostSerializer,
)

cache = caches['default']  # RedisCache
logger = logging.getLogger('myblog')


class CachedListAPIView(ListAPIView):
    @method_decorator(cache_page(settings.CACHE_TIMEOUT))
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)


# class PostViewSet(CachedListAPIView, viewsets.ReadOnlyModelViewSet):
class PostViewSet(viewsets.ReadOnlyModelViewSet):
    """文章列表和详情页面视图"""
    serializer_class = PostSerializer
    pagination_class = HomePagePagination
    lookup_field = 'slug'

    def get_queryset(self):
        # 减少查询次数
        qs = Post.objects.select_related('category').prefetch_related('tags')
        return qs.published_posts().order_by('-created')  # 只显示`发表`状态的文章

    # 重写retrieve方法，当用户访问详情页面后更新浏览次数
    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        instance.num_views += 1
        instance.save()

        # 把数据库中的markdown格式内容转换为HTML再序列化
        # https://www.jianshu.com/p/442 bc083c835
        instance.content = markdown.markdown(
            instance.content,
            extensions=[
                'markdown.extensions.extra',
                'markdown.extensions.codehilite',
                'markdown.extensions.toc',
            ]
        )
        serializer = self.get_serializer(instance)

        # 获得上一篇和下一篇文章的slug(根据发布日期前后)
        # prev_post = Post.objects.get_prev_post(instance)
        next_post = Post.objects.get_next_post(instance)
        ret = serializer.data
        ret.update({
            # 'prev_post': prev_post,
            'next_post': next_post,
        })
        return Response(ret)


# class CategoriesView(CachedListAPIView):
class CategoriesView(ListAPIView):
    serializer_class = CategorySerializer
    queryset = Category.objects.all()[:settings.NAV_SIZE]


# class CategoryPostsView(CachedListAPIView):
class CategoryPostsView(ListAPIView):
    serializer_class = PostsSerializer
    pagination_class = PostsPagination

    def get_queryset(self):
        category_name = self.kwargs.get('category_name')
        if not category_name:
            logger.error(f'{self.__class__.__name__}: 查找{category_name}失败')
            raise exceptions.NotFound(detail='页面未找到')

        # 目录不存在或者目录下没有文章都会返回空的查询集(QuerySet)
        return Post.objects.published().filter(category__name=category_name).all()


# class TagPostsView(CachedListAPIView):
class TagPostsView(ListAPIView):
    pagination_class = PostsPagination
    serializer_class = PostsSerializer

    def get_queryset(self):
        tag_name = self.kwargs.get('tag_name')
        if not tag_name:
            logger.error(f'{self.__class__.__name__}: 查找{tag_name}失败')
            raise exceptions.NotFound(detail='页面未找到')

        return Post.objects.published().filter(tags__name=tag_name).all()


# class PopularPostsView(CachedListAPIView):
class PopularPostsView(ListAPIView):
    serializer_class = PopularPostSerializer
    pagination_class = PostsPagination
    queryset = Post.objects.popular_posts()


def _aggregate_posts():
    # 先按文章个数降序排，如果文章个数一样，按照发表时间降序排
    # 需要过滤掉文章状态为`草稿`的文章（status为'd')
    # 显示10条记录即可limit=10

    # 需要使用MySQL不然报错！！
    # django.db.utils.OperationalError: no such # function: date_format
    sql = '''
SELECT 
    date_format(created, '%Y-%m') AS df, 
    COUNT(id) AS post_count 
FROM
     myblog_post
WHERE 
    status != 'd' 
GROUP BY
     df 
ORDER BY 
    post_count DESC, df DESC
LIMIT 10;  
'''
    with connection.cursor() as cur:
        cur.execute(sql)
        results = cur.fetchall()

    # [('2020', '9', 2), ('2020', '8', 1)]
    items = []
    for date, post_count in results:
        year, month = date.split('-')
        if month.startswith('0'):
            month = month[1]
        items.append((year, month, post_count))

    return items


def get_archives_view(request):
    """获取文章归档日期列表，按年月分组"""
    items = _aggregate_posts()

    '''
    cache_key = 'get_archive_view'
    try:
        results = cache.get(cache_key, None)

        if results is None:  # 缓存中还没有数据，先缓存
            results = [{'year': y, 'month': m, 'post_count': pc} for y, m, pc in items]
            cache.set(cache_key, json.dumps(results, ensure_ascii=False), settings.CACHE_TIMEOUT)
        else:
            results = json.loads(results)
    except redis.exceptions.ConnectionError:  # 如果redis突然宕机，返回空？？
        logger.error('redis连接失败！')
        # logging
        # results = [{'year': y, 'month': m, 'post_count': pc} for y, m, pc in items]
        return JsonResponse({'results': []})
    '''

    results = [{'year': y, 'month': m, 'post_count': pc} for y, m, pc in items]
    # 封装到字典中，比列表安全，注意前端获取数据response.data.results
    return JsonResponse({'results': results})


# https://docs.djangoproject.com/en/3.2/topics/cache/#accessing-the-cache
def get_links_view(request):
    """获取文章归档日期列表，按年月分组,显示5条记录"""
    '''
    cache_key = 'get_links_view'
    try:
        results = cache.get(cache_key, None)

        if results is None:  # 缓存中还没有数据，先缓存
            values = Link.objects.values('id', 'name', 'url').order_by('-ordering')[:5]
            results = list(values)
            cache.set(cache_key, json.dumps(results, ensure_ascii=False), settings.CACHE_TIMEOUT)
        else:
            results = json.loads(results)
    except redis.exceptions.ConnectionError:
        # logging
        return JsonResponse({'results': []})
    '''

    # 测试时需要ordering字段
    values = Link.objects.values('id', 'name', 'url', 'ordering').order_by('-ordering')[:5]
    return JsonResponse({'results': list(values)})


class ArchivePostsView(CachedListAPIView):
    """
    获取`year`年`month`月发表的文章列表数据

    结果集：
    {
    "num_pages": 1,
    "count": 2,
    "results": [
        {
            "title": "title_27",
            "pub_date": "2021年03月30日 12:37",
            "slug": "title_27"
        },
        {
            "title": "Python类的创建过程2",
            "pub_date": "2021年03月30日 12:37",
            "slug": "Python类的创建过程2"
        }
    ]
    }
    """
    serializer_class = PostsSerializer
    pagination_class = PostsPagination

    def get_queryset(self):
        try:
            year = int(self.kwargs.get('year'))
            month = int(self.kwargs.get('month'))
            qs = Post.objects.filter(
                created__year=year,
                created__month=month,
                status=Post.PUBLISHED
            )
            return qs
        except Exception as e:
            logger.error(f'{self.__class__.__name__}: {str(e)}')
            raise exceptions.NotFound(detail='页面未找到')


# 根据文章标题 标签 分类查找 （根据内容？？暂不实现）
class SearchPostsView(ListAPIView):
    serializer_class = PostsSerializer
    pagination_class = PostsPagination

    def get_queryset(self):
        keyword = self.request.query_params.get('keyword', None)
        if not keyword:  # keyword也可能是''
            # 结果集:
            # {
            #     "num_pages": 1,
            #     "count": 0,
            #     "results": []
            # }
            return Post.objects.none()

        # 去掉首尾空白字符
        keyword = keyword.strip()

        try:
            qs = Post.objects.published().filter(
                Q(title__icontains=keyword) | Q(category__name__icontains=keyword) | Q(
                    tags__name__iexact=keyword)).distinct()
            return qs
        except Exception as e:
            logger.error(f'{self.__class__.__name__}: {str(e)}')
            raise exceptions.NotFound(detail='页面未找到')
